/*
  displays.h

   Licensed to the Apache Software Foundation (ASF) under one
   or more contributor license agreements.  See the NOTICE file
   distributed with this work for additional information
   regarding copyright ownership.  The ASF licenses this file
   to you under the Apache License, Version 2.0 (the
   "License"); you may not use this file except in compliance
   with the License.  You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing,
   software distributed under the License is distributed on an
   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   KIND, either express or implied.  See the License for the
   specific language governing permissions and limitations
   under the License.
 
   Created on: Jul 16, 2009
 
 */

#ifndef RENDERERS_H_
#define RENDERERS_H_

#include <boost/thread/mutex.hpp>
#include <boost/thread/locks.hpp>

#include <QImage>
#include <QEvent>
#include <QWidget>
#include <QApplication>

#include <wells/v4lcameras.h>

namespace crossEyed {

namespace sinks {

namespace intoQt {

using namespace crossEyed ;
using namespace core ;
using namespace wells ;

class TV4LPixelAdapter
{
protected:
   TV4L::TFrame * pFrame ;
   TV4L::TYUYV  * pCurMacroPixel ;

public:

   inline TV4LPixelAdapter( TV4L::TFrame * pFrame )
      : pFrame( pFrame ),
        pCurMacroPixel( pFrame->data ) {};

   inline void toTarget( QRgb aDestPixel[] )
   {
      TColor RGB ;

      TColor( pCurMacroPixel->Y0, pCurMacroPixel->U0, pCurMacroPixel->V0 ).convertToRGB( RGB ) ;
      aDestPixel[0] = qRgb( RGB.R, RGB.G, RGB.B ) ;

      TColor( pCurMacroPixel->Y1, pCurMacroPixel->U0, pCurMacroPixel->V0 ).convertToRGB( RGB ) ;
      aDestPixel[1] = qRgb( RGB.R, RGB.G, RGB.B ) ;
   }

   inline TV4LPixelAdapter & operator ++ (int)
   {
      pCurMacroPixel++ ;
      return *this ;
   }

   static inline size_t getBytesPerMacroPixel()
   {
      return sizeof( TV4L::TYUYV ) ;
   }

   static inline size_t getPixelsPerMacroPixel()
   {
      return 2 ;
   }
};

class TV4LBlackAndWhitePixelAdapter
{
protected:
   TV4L::TFrame * pFrame ;
   TV4L::TYUYV  * pCurMacroPixel ;

public:

   inline TV4LBlackAndWhitePixelAdapter( TV4L::TFrame * pFrame )
      : pFrame( pFrame ),
        pCurMacroPixel( pFrame->data ) {};

   inline void toTarget( QRgb aDestPixel[] )
   {
      TColor RGB ;

      TColor( pCurMacroPixel->Y0, 128, 128 ).convertToRGB( RGB ) ;
      aDestPixel[0] = qRgb( RGB.R, RGB.G, RGB.B ) ;

      TColor( pCurMacroPixel->Y1, 128, 128 ).convertToRGB( RGB ) ;
      aDestPixel[1] = qRgb( RGB.R, RGB.G, RGB.B ) ;
   }

   inline TV4LBlackAndWhitePixelAdapter & operator ++ (int)
   {
      pCurMacroPixel++ ;
      return *this ;
   }

   static inline size_t getBytesPerMacroPixel()
   {
      return sizeof( TV4L::TYUYV ) ;
   }

   static inline size_t getPixelsPerMacroPixel()
   {
      return 2 ;
   }
};

class TToLuminancePixelAdapter
{
protected:
   TByteFrameBuffer * pFrame ;
   unsigned char *    pCurMacroPixel ;

public:

   inline TToLuminancePixelAdapter( TByteFrameBuffer * pFrame )
      : pFrame( pFrame ),
        pCurMacroPixel( pFrame->getBuffer() ) {};

   inline void toTarget( QRgb aDestPixel[] )
   {
      aDestPixel[0] = qRgb( *pCurMacroPixel,
                            *pCurMacroPixel,
                            *pCurMacroPixel ) ;
   }

   inline TToLuminancePixelAdapter & operator ++ (int)
   {
      pCurMacroPixel++ ;
      return *this ;
   }

   static inline size_t getBytesPerMacroPixel()
   {
      return sizeof( unsigned char ) ;
   }

   static inline size_t getPixelsPerMacroPixel()
   {
      return 1 ;
   }
};

class TAbstractRenderer
{
public:

   QImage                     image ;
   QWidget *                  pListener ;

   boost::mutex               imageMutex ;


   const static int NewFrameEvent = QEvent::User + 1;

   TAbstractRenderer( QSize size )
      : image( size.width(), size.height(), QImage::Format_RGB32 ),
        pListener( NULL ) { } ;

   virtual ~TAbstractRenderer() { } ;

   void setListener( QWidget *pListener )
   {
      this->pListener = pListener ;
   }

   //
   // Implement callable concept
   //
   inline void operator()()
   {
      eventLoop() ;
   }

   virtual void eventLoop() = 0 ;

   QImage & getImage() { return image; } ;
   boost::mutex & getImageMutex() { return imageMutex; } ;
};

template<class SOURCEFRAME, class PIXELADAPTER>
class TRenderer : public TAbstractRenderer
{
protected:
   TOperator<SOURCEFRAME> *  pSource ;
   TDataQueue<SOURCEFRAME> * pSourceQueue ;

public:

   TRenderer( TOperator<SOURCEFRAME> * pSource, QSize size )
      : TAbstractRenderer( size ),
        pSource( pSource ),
        pSourceQueue( pSource->getDataQueue() )
   { } ;

   virtual void eventLoop() ;
};

template<class SOURCEFRAME, class PIXELADAPTER>
void TRenderer<SOURCEFRAME,PIXELADAPTER>::eventLoop()
{
   BOOST_DEDUCED_TYPENAME TDataQueue<SOURCEFRAME>::TSubscription  subscription ;
   boost::unique_lock<boost::mutex>                               imageLock( imageMutex, boost::defer_lock );

   QSize  size ;
   QRgb * pDestPixel ;

   while( !boost::this_thread::interruption_requested() )
   {
      try
      {
         pSourceQueue->renew( subscription ) ;
      }
      catch( boost::thread_interrupted interrupt )
      {
         break ;
      }

      //
      // Now that we got a frame, get the imageMutex and
      // transfer the image into Qt space
      //
      imageLock.lock() ;

      size = image.size() ;
      //
      // Sanity check! But beware: V2L: 2 bytes per pixel vs. Qt 4 bytes per pixel
      //
      XI_ASSERT_HARD( size.height() * size.width() * PIXELADAPTER::getBytesPerMacroPixel() ==
                         subscription.pRep->getSize() * PIXELADAPTER::getPixelsPerMacroPixel() );

      PIXELADAPTER pixelAdapter( subscription.pRep );

      for( int y = 0;
           y < size.height();
           y ++ )
      {
         pDestPixel = (QRgb *)image.scanLine( y ) ;

         //
         // The RGB format used by Qt uses a one integer per pixel representation
         // while V4L uses YUYV format (YUV 4:2:2). Per iteration of the following loop
         // we will transfer two pixels from the V4L buffer into two pixels in Qt space.
         //
         for( int x = 0;
              x < size.width();
              x += PIXELADAPTER::getPixelsPerMacroPixel() )
         {
            pixelAdapter.toTarget( pDestPixel ) ;

            pixelAdapter++ ;
            pDestPixel += PIXELADAPTER::getPixelsPerMacroPixel() ;
         }
      }

      imageLock.unlock() ;

      //
      // We transferred one image into a Qt image - make it available
      //
      if( pListener != NULL )
      {
         QCoreApplication::postEvent( pListener, new QEvent( (QEvent::Type)NewFrameEvent ) ) ;
      }
   }

   if( subscription.pRep != NULL )
   {
      pSourceQueue->unsubscribe( subscription ) ;
   }
}

typedef TRenderer<TV4L::TFrame, TV4LPixelAdapter> TRawRenderer ;
typedef TRenderer<TV4L::TFrame, TV4LBlackAndWhitePixelAdapter> TRawBlackAndWhiteRenderer ;
typedef TRenderer<TByteFrameBuffer, TToLuminancePixelAdapter> TByteFrameRenderer ;

}}}
#endif /* RENDERERS_H_ */
